package org.naxx.security.naxxlet.model.combiner;

import java.util.List;

import org.naxx.security.algorithm.RuleCombiningAlgorithm;
import org.naxx.security.common.Decision;
import org.naxx.security.model.Rule;
import org.naxx.security.model.request.Request;

/**
 * Given set of rules, rule combining algorithm and request context, 
 * this class will evaluate <i>Decision</i> by combining results
 * of evaluation of all rules in the set.
 */
public final class RulesCombiner {
	
	private RuleEvaluator ruleEvaluator = new RuleEvaluator();

	public Decision evaluate(List<Rule> rules, Request request, RuleCombiningAlgorithm ruleCombiningAlgorithm) {
		if(RuleCombiningAlgorithm.DENY_OVERRIDES.equals(ruleCombiningAlgorithm)) {
			return this.denyOverrides(rules, request);
		}
		else if(RuleCombiningAlgorithm.ORDERED_DENY_OVERRIDES.equals(ruleCombiningAlgorithm)) {
			return this.orderedDenyOverrides(rules, request);
		}
		else if(RuleCombiningAlgorithm.PERMIT_OVERRIDES.equals(ruleCombiningAlgorithm)) {
			return this.permitOverrides(rules, request);
		}
		else if(RuleCombiningAlgorithm.ORDERED_PERMIT_OVERRIDES.equals(ruleCombiningAlgorithm)) {
			return this.orderedPermitOverrides(rules, request);
		}
		else if(RuleCombiningAlgorithm.FIRST_APPLICABLE.equals(ruleCombiningAlgorithm)) {
			return this.firstApplicable(rules, request);
		}
		else {
			throw new IllegalArgumentException("Illegal rule combining algorithm '" 
					+ ruleCombiningAlgorithm.name() + "'");
		}
	}
	
	Decision orderedDenyOverrides(List<Rule> rules, Request request) {
		return this.denyOverrides(rules, request);
	}
	
	Decision denyOverrides(List<Rule> rules, Request request) {
		boolean atLeastOneError = false;
		boolean potentialDeny = false;
		boolean atLeastOnePermit = false;
		for (Rule rule : rules) {
			Decision decision = this.ruleEvaluator.evaluate(rule, request);
			if(Decision.DENY.equals(decision)) {
				return Decision.DENY;
			}
			if(Decision.PERMIT.equals(decision)) {
				atLeastOnePermit = true;
				continue;
			}
			if(Decision.NOT_APPLICABLE.equals(decision)) {
				continue;
			}
			if(Decision.INDETERMINATE.equals(decision)) {
				atLeastOneError = true;
				if(Decision.DENY.equals(rule.getEffect())) {
					potentialDeny = true;
				}
				continue;
			}
		}
		if(potentialDeny) {
			return Decision.INDETERMINATE;
		}
		if(atLeastOnePermit) {
			return Decision.PERMIT;
		}
		if(atLeastOneError) {
			return Decision.INDETERMINATE;
		}
		return Decision.NOT_APPLICABLE;
	}
	
	Decision permitOverrides(List<Rule> rules, Request request) {
		boolean atLeastOneError = false;
		boolean potentialPermit = false;
		boolean atLeastOneDeny = false;
		for(Rule rule : rules) {
			Decision decision = this.ruleEvaluator.evaluate(rule, request);
			if(Decision.DENY.equals(decision)) {
				atLeastOneDeny = true;
				continue;
			}
			if(Decision.PERMIT.equals(decision)) {
				return Decision.PERMIT;
			}
			if(Decision.NOT_APPLICABLE.equals(decision)) {
				continue;
			}
			if(Decision.INDETERMINATE.equals(decision)) {
				atLeastOneError = true;
				if(Decision.PERMIT.equals(rule.getEffect())) {
					potentialPermit = true;
				}
				continue;
			}
		}
		if(potentialPermit) {
			return Decision.INDETERMINATE;
		}
		if(atLeastOneDeny) {
			return Decision.DENY;
		}
		if(atLeastOneError) {
			return Decision.INDETERMINATE;
		}
		return Decision.NOT_APPLICABLE;
	}

	Decision orderedPermitOverrides(List<Rule> rules, Request request) {
		return this.permitOverrides(rules, request);
	}
	
	Decision firstApplicable(List<Rule> rules, Request request) {
		for(Rule rule : rules) {
			Decision decision = this.ruleEvaluator.evaluate(rule, request);
			if(Decision.DENY.equals(decision)) {
				return Decision.DENY;
			}
			if(Decision.PERMIT.equals(decision)) {
				return Decision.PERMIT;
			}
			if(Decision.NOT_APPLICABLE.equals(decision)) {
				continue;
			}
			if(Decision.INDETERMINATE.equals(decision)) {
				return Decision.INDETERMINATE;
			}
		}
		return Decision.NOT_APPLICABLE;
	}
}
